package com.acaomei.skype.interceptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.acaomei.skype.annotations.MyInsfulateSign;
import com.acaomei.skype.util.SqlInsulateUtil;

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), 
	@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyInsulateHelper implements Interceptor {

	private static final Logger logger = LoggerFactory.getLogger(MyInsulateHelper.class);
	
	// 设置测试数据字段标志的set方法名
	private String testSignMethod;
	
	private boolean filterTestData;
	
	public MyInsulateHelper(){}
	
	public MyInsulateHelper(String testSignMethod){
		this.testSignMethod = testSignMethod;
	}
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object[] args = invocation.getArgs();
		MappedStatement ms = (MappedStatement) args[0];
		SqlCommandType sqlCommandType = ms.getSqlCommandType();
		// 如果不是插入方法，则直接返回
		if (SqlCommandType.INSERT.equals(sqlCommandType)) {
			interceptByInsert(invocation);
		}
		
		if (SqlCommandType.SELECT.equals(sqlCommandType)) {
			interceptBySelect(invocation);
		}
		
		return invocation.proceed();
	}

	@Override
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		
	}

	public static void startInsulate(String user) {
		if (StringUtils.isBlank(user)) {
			return;
		}
		SqlInsulateUtil.setUser(user);
	}
	
	/**
	 * 处理查询语句拦截
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	private Object interceptBySelect(Invocation invocation) throws Throwable {
		if(!filterTestData) {
			// 查询数据时，不过滤测试数据
			return invocation.proceed();
		}
		System.out.println(testSignMethod);
		System.out.println(filterTestData);
		Object[] args = invocation.getArgs();
		Object parameter = args[1];
		if(parameter instanceof Map) {
			Map<String,Object> ds = (Map) parameter;
			ds.put("isTestData", SqlInsulateUtil.TestSignEnum.NOT_TEST.getCode());
		}else {
			Method[] methods = parameter.getClass().getMethods();
			for (Method method : methods) {
				if (testSignMethod.equals(method.getName())) {
					method.invoke(parameter, SqlInsulateUtil.TestSignEnum.NOT_TEST.getCode());
					break;
				}
			}
		}
		return invocation.proceed();
	}
	
	/**
	 * 处理插入语句拦截
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	private Object interceptByInsert(Invocation invocation) throws Throwable {
		Object[] args = invocation.getArgs();
		//MappedStatement ms = (MappedStatement) args[0];
		try {
			List<String> insulateMarkList = SqlInsulateUtil.getInsulateMarkList();
			// 没设置测试名单，则直接通过
			if (insulateMarkList == null) {
				return invocation.proceed();
			}

			Object parameter = args[1];
			// 如果参数对象是map则不作处理
			if (parameter instanceof Map) {
				return interceptForMap(invocation);
			}

			// 如果参数对象是List则不作处理
			if (parameter instanceof List) {
				return invocation.proceed();
			}

			return interceptForEntity(invocation);
		} catch (Exception e) {
			logger.error("测试数据隔断拦截执行错误！错误原因：" + e.getMessage());
			e.printStackTrace();
			return invocation.proceed();
		} finally {
			SqlInsulateUtil.clearUser();
		}
	}
	
	/**
	 * 参数是map类型处理
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	private Object interceptForMap(Invocation invocation) throws Throwable {
		boolean isInsulate = true;
		if (StringUtils.isBlank(SqlInsulateUtil.getUser())
				|| !SqlInsulateUtil.getInsulateMarkList().contains(SqlInsulateUtil.getUser())) {
			isInsulate = false;
		}
		Object[] args = invocation.getArgs();
		Object parameter = args[1];
		//MappedStatement ms = (MappedStatement) args[0];
		// 如果参数对象是map则不作处理
		Map<String, Object> parameterMap = (Map<String, Object>) parameter;
		String key = SqlInsulateUtil.getKeyOrNull(parameterMap);
		Object value = parameterMap.get(key);
		if (value instanceof List) {
			List<Object> valueList = (List) value;
			if(valueList ==null || valueList.size() <= 0) {
				return invocation.proceed();
			}
			for (Object object : valueList) {
				// 如果object是map
				if (object instanceof Map) {
					return invocation.proceed();
				}

				// 如果object是实体类
				Method[] methods = object.getClass().getMethods();
				for (Method method : methods) {
					if (testSignMethod.equals(method.getName())) {
						if(isInsulate) {
							method.invoke(object, SqlInsulateUtil.TestSignEnum.TEST.getCode());
						}else {
							method.invoke(object, SqlInsulateUtil.TestSignEnum.NOT_TEST.getCode());
						}
						break;
					}
				}
			}
		}
		
//		Configuration configuration = ms.getConfiguration();
//		Object target = invocation.getTarget();
//		StatementHandler handler = configuration.newStatementHandler((Executor) target, ms, parameter,
//				RowBounds.DEFAULT, null, null);
		// 记录SQL
		//BoundSql boundSql = handler.getBoundSql();
		//logger.info("sql语句：{}", boundSql.getSql());
		// 执行真正的方法
		Object result = invocation.proceed();
		// 记录影响行数
		//logger.info("影响行数:{}", Integer.valueOf(Integer.parseInt(result.toString())));
		return result;
	}
	
	/**
	 * 参数是实体类型处理
	 * @param invocation
	 * @return
	 * @throws Throwable
	 */
	private Object interceptForEntity(Invocation invocation) throws Throwable {
		Object[] args = invocation.getArgs();
		Object parameter = args[1];
		//MappedStatement ms = (MappedStatement) args[0];
		String user = SqlInsulateUtil.getUser();
		if (StringUtils.isBlank(user)) {
			// 如果当前线程没有设置指定值，则需校验插入的数据中，是否含有MyInsfulateSign主键，获取注解对应变量的值进行校验
			Field[] fields = parameter.getClass().getDeclaredFields();
			boolean isContainSignAnnotation = false;
			for (Field field : fields) {
				if (field.isAnnotationPresent(MyInsfulateSign.class)) {
					isContainSignAnnotation = true;
					String fieldName = field.getName();
					String getter = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
					Method method = parameter.getClass().getMethod(getter, new Class[] {});
					Object signValue = method.invoke(parameter, new Object[] {});
					// 没匹配到测试名单则直接通过
					if (signValue != null
							&& !SqlInsulateUtil.getInsulateMarkList().contains(String.valueOf(signValue))) {
						logger.info("-------- 不是测试人员 -----");
						return invocation.proceed();
					}
					logger.info(field + "含有注解对象MyInsfulateSign，值：" + signValue);
					break;
				}
			}
			if (!isContainSignAnnotation) {
				return invocation.proceed();
			}
		} else {
			// 没匹配到测试名单则直接通过
			if (!SqlInsulateUtil.getInsulateMarkList().contains(user)) {
				logger.info("-------- 不是测试人员 -----");
				return invocation.proceed();
			}
		}

		Method[] methods = parameter.getClass().getMethods();
		for (Method method : methods) {
			if (testSignMethod.equals(method.getName())) {
				method.invoke(parameter, SqlInsulateUtil.TestSignEnum.TEST.getCode());
				break;
			}
		}
//		Configuration configuration = ms.getConfiguration();
//		Object target = invocation.getTarget();
//		StatementHandler handler = configuration.newStatementHandler((Executor) target, ms, parameter,
//				RowBounds.DEFAULT, null, null);
		// 记录SQL
		//BoundSql boundSql = handler.getBoundSql();
		//logger.info("sql语句：{}", boundSql.getSql());
		// 执行真正的方法
		Object result = invocation.proceed();
		// 记录影响行数
		//logger.info("影响行数:{}", Integer.valueOf(Integer.parseInt(result.toString())));
		return result;
	}

	public void setTestSignMethod(String testSignMethod) {
		this.testSignMethod = testSignMethod;
	}
	
	public void setFilterTestData(boolean filterTestData) {
		this.filterTestData = filterTestData;
	}
	
}
